/*
 *  Author: Gary Kelly, gkelly2008@my.fit.edu
 *  Author: John Stevens, jstevens@fit.edu
 *  Class:  CSE 4232 - Network Programming
 *  Program: SQlite database synchronization server thread
 */
 
#include <iostream>
//#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <errno.h>
#include <signal.h>
#include <sqlite3.h>
#include <libtasn1.h>


using namespace std;

const ASN1_ARRAY_TYPE database_asn1_tab[]={
  {"DatabaseMessagesModule",536875024,0},
  {0,1610612748,0},
  {"iso",1073741825,"1"},
  {"identified-organization",1073741825,"3"},
  {"dod",1073741825,"6"},
  {"internet",1073741825,"1"},
  {"security",1073741825,"5"},
  {"mechanisms",1073741825,"5"},
  {"sqliteserver",1,"108"},
  {"SyncRequest",1610612741,0},
  {"lastSnapshot",1082130449,0},
  {"tableNames",536870923,0},
  {0,2,"TableName"},
  {"Database",1610612741,0},
  {"tables",1610612747,0},
  {0,2,"Table"},
  {"name",1073741826,"DatabaseName"},
  {"snapshot",8388625,0},
  {"Table",1610612741,0},
  {"name",1073741826,"TableName"},
  {"fields",1610612747,0},
  {0,2,"FieldName"},
  {"fieldTypes",1610612747,0},
  {0,2,"FieldType"},
  {"rows",536870923,0},
  {0,2,"Row"},
  {"Row",1610612747,0},
  {0,2,"FieldValue"},
  {"FieldValue",1610612754,0},
  {"value",1073741831,0},
  {"empty",20,0},
  {"TableName",1610620930,"UTF8String"},
  {0,4104,"0"},
  {"DatabaseName",1610620930,"UTF8String"},
  {0,4104,"1"},
  {"FieldName",1610620930,"UTF8String"},
  {0,4104,"2"},
  {"FieldType",1610620930,"PrintableString"},
  {0,4104,"3"},
  {"UTF8String",1610620935,0},
  {0,4360,"12"},
  {"PrintableString",536879111,0},
  {0,4360,"19"},
  {0,0,0}
};

#define BUF_SIZE 512
#define MAX_THREADS 10

int err = 0;
ASN1_TYPE resp = ASN1_TYPE_EMPTY;

using namespace std;

//a function for saving the results of the SQL query
static int writeFieldInfo (void *asn, int argc, char* argv[], char* azColName[]) {
	//fields SEQUENCE OF FieldName
	if ( (err = asn1_write_value (resp, "fields", "NEW", 0)) != ASN1_SUCCESS) {
        cerr << "CB - write_value fields.NEW error: " << asn1_strerror (err) << endl;
        return 1;
	}
	//fields SEQUENCE OF FieldName
	if ( (err = asn1_write_value (resp, "fields.?LAST", argv[1] ? argv[1] : "NULL", 0)) != ASN1_SUCCESS) {
        cerr << "CB - write_value fields.?LAST error: " << asn1_strerror (err) << endl;
        return 1;
	}

	//fieldTypes SEQUENCE OF FieldType
	if ( (err = asn1_write_value (resp, "fieldTypes", "NEW", 0)) != ASN1_SUCCESS) {
        cerr << "CB - write_value fields.NEW error: " << asn1_strerror (err) << endl;
        return 1;
	}
	//fieldTypes SEQUENCE OF FieldType
	if ( (err = asn1_write_value (resp, "fieldTypes.?LAST", argv[2] ? argv[2] : "NULL", 0)) != ASN1_SUCCESS) {
        cerr << "CB - write_value fields.?LAST error: " << asn1_strerror (err) << endl;
        return 1;
	}

	return 0;
}

//a function for saving the results of the SQL query
static int writeRows (void *asn, int argc, char* argv[], char* azColName[]) {
	//rows SEQUENCE OF Rows
	if ( (err = asn1_write_value (resp, "rows", "NEW", 0)) != ASN1_SUCCESS) {
        cerr << "CB - write_value rows NEW error: " << asn1_strerror (err) << endl;
        return 1;
    }

	//for each column entry...
	for (int i = 0; i < argc; i++) {
		string fieldName = azColName[i];
		string fieldValue = argv[i] ? argv[i] : "NULL";

		//Row SEQUENCE OF FieldValues
		if ( (err = asn1_write_value (resp, "rows.?LAST", "NEW", 0)) != ASN1_SUCCESS) {
			cerr << "CB - write_value rows.?LAST.NEW error: " << asn1_strerror (err) << endl;
			return 1;
		}

		//FieldValue CHOICE value OCTET STRING
		if (argv[i]) {
			if ( (err = asn1_write_value (resp, "rows.?LAST.?LAST", "value", 0)) != ASN1_SUCCESS) {
				cerr << "CB - write_value rows.?LAST.?LAST.value error: " << asn1_strerror (err) << endl;
				return 1;
			}
			
			if ( (err = asn1_write_value (resp, "rows.?LAST.?LAST.value", fieldValue.c_str (), 0)) != ASN1_SUCCESS) {
				cerr << "CB - write_value rows.?LAST.?LAST.value error: " << asn1_strerror (err) << endl;
				return 1;
			}
		//FieldValue CHOICE empty NULL
		} else {
			if ( (err = asn1_write_value (resp, "rows.?LAST.?LAST", "empty", 0)) != ASN1_SUCCESS) {
				cerr << "CB - write_value rows.?LAST.?LAST.empty error: " << asn1_strerror (err) << endl;
				return 1;
			}
		}
	}

	return 0;
}

void* serverRun (void*) {
	//networking
	int serverSocket;
	socklen_t addrlen;
	struct sockaddr_in serverAddr;
	struct sockaddr_in clientAddr;
	
	//threading
	pthread_t thID[MAX_THREADS];
	
	//signaling
	sigset_t sigSet;
	sigemptyset (&sigSet);
	sigaddset (&sigSet, SIGINT);
	pthread_sigmask (SIG_BLOCK, &sigSet, NULL); 

	// create a socket for the server
	if ( (serverSocket = socket (AF_INET, SOCK_STREAM, 0)) == -1) {
		cerr << "socket error: " << errno;
		sqlite3_close (db);
		pthread_exit (NULL);
	}

	// clear the socket (required to use it)
	memset (&serverAddr, 0, sizeof (serverAddr));
	// complete the socket structure
	serverAddr.sin_family = AF_INET;
	serverAddr.sin_addr.s_addr = INADDR_ANY;
	serverAddr.sin_port = htons (port);

	// bind the socket to the port
	if (bind (serverSocket, (struct sockaddr *) &serverAddr, sizeof (serverAddr)) == -1) {
		cerr << "bind error: " << errno;
		sqlite3_close (db);
		pthread_exit (NULL);
	}

	// tell the socket to listen for connections
	if (listen (serverSocket, 5) == -1) {
		cerr << "listen error: " << errno;
		sqlite3_close (db);
		pthread_exit (NULL);
	}

	//keep looking for connections
	while (1) {
		int clientSocket;
		bool quit = false;
		// connect to a client
		addrlen = sizeof (clientAddr); 
		if ( (clientSocket = accept (serverSocket, (struct sockaddr *)  &clientAddr, &addrlen)) == -1) {
			cerr << "accept error: " << errno;
			sqlite3_close (db);
			pthread_exit (NULL);
		}
		//We got a connection
		cout << "Client: " << inet_ntoa (clientAddr.sin_addr) << " connected" << endl;

		while (!quit) {
			int messageLength = BUF_SIZE;
			int recvSize = 0;
			char buffer[BUF_SIZE];
			string requestedTableName;

			//sqlite definitions
			char* zErrMsg = 0;
			string command;

			//asn definitions
			ASN1_TYPE def = ASN1_TYPE_EMPTY;
			ASN1_TYPE req = ASN1_TYPE_EMPTY;
			char errorDesc[ASN1_MAX_ERROR_DESCRIPTION_SIZE];

			// get a message from the client
			if ( (recvSize = recv (clientSocket, buffer, BUF_SIZE, 0)) == -1) {
				cerr << "recv error: " << errno;
				sqlite3_close (db);
				pthread_exit (NULL);
			}

			// look for a quit message
			if (strncmp ( (char*)buffer, "quit", 4) == 0) {	
				cout << "Client: " << inet_ntoa (clientAddr.sin_addr) << " disconnected" << endl;
				quit = true;
				continue;
			}

			//we got a query
			cout << "Got: " << recvSize << " bytes." << endl;

			//create an ASN1 structure for the client's message
			if ( (err = asn1_array2tree (database_asn1_tab, &def, errorDesc)) != ASN1_SUCCESS) {
				cerr << "array2tree error: " << asn1_strerror (err) << endl;
				break;
			}

			//create a new structure for the request from the client
			if ( (err = asn1_create_element (def, "DatabaseMessagesModule.SyncRequest", &req)) != ASN1_SUCCESS) {
				cerr << "create_element error: " << asn1_strerror (err) << endl;
				break;
			}

			//decoding
			if ( (err = asn1_der_decoding (&req, buffer, recvSize, errorDesc)) != ASN1_SUCCESS) {
                cerr << "der_decoding error: " << asn1_strerror (err) << endl;
                break;
            }

			//get the requested table name
			if ( (err = asn1_read_value (req, "tableNames.?1", buffer, &messageLength)) != ASN1_SUCCESS) {
				cerr << "read_value error: " << asn1_strerror (err) << endl;
				break;
			}

			requestedTableName = string (buffer, messageLength);
			cout << "Request: " << requestedTableName.c_str () << endl;
			cout << "Building database image... ";
			cout.flush ();

			//create a new structure for the response
			if ( (err = asn1_create_element (def, "DatabaseMessagesModule.Table", &resp)) != ASN1_SUCCESS) {
				cerr << "create_element error: " << asn1_strerror (err) << endl;
				break;
			}

			//name TableName
			if ( (err = asn1_write_value (resp, "name", requestedTableName.c_str (), 0)) != ASN1_SUCCESS) {
				cerr << "write_value name error: " << asn1_strerror (err) << endl;
				break;
			}

			//attempt to gather the fields and fieldTypes
			command = "PRAGMA table_info(" + requestedTableName + ");";
			err = sqlite3_exec (db,			//an open database
					    command.c_str (),	//SQL command
					    writeFieldInfo,			//callback function
					    &requestedTableName,				//1st argument to callback
					    &zErrMsg);			//error msg written here
			if (err != SQLITE_OK) {
				cerr << "SQL error: " + string (zErrMsg) << endl;
				sqlite3_free (zErrMsg);
			}
			
			//attempt to gather the rows
			command = "SELECT * FROM " + requestedTableName;
			//attempt to gather the FieldNames
			err = sqlite3_exec (db,			//an open database
					    command.c_str (),	//SQL command
					    writeRows,	//callback function
					    &resp,				//1st argument to callback
					    &zErrMsg);			//error msg written here
			if (err != SQLITE_OK) {
				cerr << "SQL error: " + string (zErrMsg) << endl;
				sqlite3_free (zErrMsg);
			}
			
			//encoding
			messageLength = BUF_SIZE;
			if ( (err = asn1_der_coding (resp, "", buffer, &messageLength, errorDesc)) != ASN1_SUCCESS) {
                cerr << "der_coding error: " << asn1_strerror (err) << endl;
                break;
            }
            cout << "Done" << endl;

			// send the response back to the client
			cout << "Sending request... ";
			if (send (clientSocket, buffer, messageLength, 0) == -1) {
				cerr << "send error: " << errno << endl;
				sqlite3_close (db);
				pthread_exit (NULL);
			}
			cout << "Sent " << messageLength << " bytes" << endl;
			
			asn1_delete_structure (&def);
			asn1_delete_structure (&req);
		} //inner while

		//clean up
		if (shutdown (clientSocket, SHUT_RDWR) == -1) {
			cerr << "shutdown error: " << errno << endl;
			sqlite3_close (db);
			pthread_exit (NULL);
		}
		close (clientSocket);
	} //outer while

	// attempt to close the database file
	asn1_delete_structure (&resp);

	pthread_exit (NULL);
}
